#include "Geometry.h"
#include "graphics/GLState.h"
#include <GL/glew.h>
using namespace Umikaze::Core;

Geometry::~Geometry() {}

InternalGeometry::~InternalGeometry() {
  if (_vao)
    glDeleteVertexArrays(1, &_vao);
  if (_vbo)
    glDeleteBuffers(1, &_vbo);
}

void CubeGeometry::draw() {
  auto &state = GlState::instance();
  if (!_vao) {
    float halfWidth = _width / 2;
    float halfHeight = _height / 2;
    float halfDepth = _depth / 2;
    /* clang-format off */
        float vertices[]
        {
            // Back face
            -halfWidth, -halfHeight, -halfDepth, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // Bottom-left
            halfWidth, halfHeight, -halfDepth, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right
            halfWidth, -halfHeight, -halfDepth, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right         
            halfWidth, halfHeight, -halfDepth, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,  // top-right
            -halfWidth, -halfHeight, -halfDepth, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,  // bottom-left
            -halfWidth, halfHeight, -halfDepth, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,// top-left
            // Front face
            -halfWidth, -halfHeight, halfDepth, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left
            halfWidth, -halfHeight, halfDepth, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,  // bottom-right
            halfWidth, halfHeight, halfDepth, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,  // top-right
            halfWidth, halfHeight, halfDepth, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right
            -halfWidth, halfHeight, halfDepth, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,  // top-left
            -halfWidth, -halfHeight, halfDepth, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,  // bottom-left
            // Left face
            -halfWidth, halfHeight, halfDepth, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
            -halfWidth, halfHeight, -halfDepth, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left
            -halfWidth, -halfHeight, -halfDepth, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,  // bottom-left
            -halfWidth, -halfHeight, -halfDepth, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left
            -halfWidth, -halfHeight, halfDepth, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,  // bottom-right
            -halfWidth, halfHeight, halfDepth, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
            // Right face
            halfWidth, halfHeight, halfDepth, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left
            halfWidth, -halfHeight, -halfDepth, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right
            halfWidth, halfHeight, -halfDepth, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right         
            halfWidth, -halfHeight, -halfDepth, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,  // bottom-right
            halfWidth, halfHeight, halfDepth, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,  // top-left
            halfWidth, -halfHeight, halfDepth, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left     
            // Bottom face
            -halfWidth, -halfHeight, -halfDepth, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
            halfWidth, -halfHeight, -halfDepth, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left
            halfWidth, -halfHeight, halfDepth, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,// bottom-left
            halfWidth, -halfHeight, halfDepth, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left
            -halfWidth, -halfHeight, halfDepth, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right
            -halfWidth, -halfHeight, -halfDepth, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
            // Top face
            -halfWidth, halfHeight, -halfDepth, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left
            halfWidth, halfHeight, halfDepth, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
            halfWidth, halfHeight, -halfDepth, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right     
            halfWidth, halfHeight, halfDepth, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
            -halfWidth, halfHeight, -halfDepth, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left
            -halfWidth, halfHeight, halfDepth, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left        
        };
    /* clang-format on */

    glGenVertexArrays(1, &_vao);
    glGenBuffers(1, &_vbo);
    state->bindArrayBuffer(_vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    state->bindVertexArray(_vao);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float),
                          (void *)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float),
                          (void *)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float),
                          (void *)(6 * sizeof(GLfloat)));
  }
  state->bindVertexArray(_vao);
  glDrawArrays(GL_TRIANGLES, 0, 36);
}

void SphereGeometry::draw() {
  auto &state = GlState::instance();
  if (!_vao) {
    std::vector<glm::vec3> vertices;
    std::vector<glm::vec3> normals;
    std::vector<glm::vec2> indices;
    generateSphere(vertices, normals, indices);
    _numVertices = vertices.size();
    uint32_t vec3Size = sizeof(glm::vec3);
    uint32_t verticesSize = _numVertices * vec3Size;
    uint32_t indicesSize = _numVertices * sizeof(glm::vec2);

    glGenVertexArrays(1, &_vao);
    glGenBuffers(1, &_vbo);
    state->bindArrayBuffer(_vbo);
    glBufferData(GL_ARRAY_BUFFER, verticesSize * 2 + indicesSize, nullptr,
                 GL_STATIC_DRAW);
    glBufferSubData(GL_ARRAY_BUFFER, 0, verticesSize, &vertices[0]);
    glBufferSubData(GL_ARRAY_BUFFER, verticesSize, verticesSize, &normals[0]);
    glBufferSubData(GL_ARRAY_BUFFER, verticesSize * 2, indicesSize,
                    &indices[0]);
    state->bindVertexArray(_vao);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),
                          (void *)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),
                          (void *)verticesSize);
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float),
                          (void *)(verticesSize * 2));
  }
  state->bindVertexArray(_vao);

  glDrawArrays(GL_TRIANGLE_STRIP, 0, _numVertices);
}

void PlaneGeometry::draw() {
  auto &state = GlState::instance();
  if (!_vao) {
    float halfWidth = _width / 2;
    float halfHeight = _height / 2;
    /* clang-format off */
    float vertices[] = {
        halfWidth,  halfHeight,  0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top right
        -halfWidth, halfHeight,  0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,                  // top left
        halfWidth,  -halfHeight, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom right
        -halfWidth, halfHeight,  0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, // top left
        -halfWidth, -halfHeight, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom left
        halfWidth,  -halfHeight, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom right
    };
    /* clang-format on */
    glGenVertexArrays(1, &_vao);
    glGenBuffers(1, &_vbo);
    state->bindArrayBuffer(_vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    state->bindVertexArray(_vao);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float),
                          (void *)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float),
                          (void *)(3 * sizeof(float)));
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float),
                          (void *)(6 * sizeof(float)));
  }
  state->bindVertexArray(_vao);
  glDrawArrays(GL_TRIANGLES, 0, 6);
}

// void QuadGeometry::draw() {
//   auto &state = GlState::instance();
//   if (!_vao) {
//     /* clang-format off */
//          float quadVertices[] = {
//             // positions        // texture Coords
//             -1.0f,  1.0f, 0.0f, 0.0f, 1.0f,
//             -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
//              1.0f,  1.0f, 0.0f, 1.0f, 1.0f,
//              1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
//         };
//     /* clang-format on */
//     glGenVertexArrays(1, &_vao);
//     glGenBuffers(1, &_vbo);
//     state->bindVertexArray(_vao);
//     state->bindArrayBuffer(_vbo);
//     glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices,
//                  GL_STATIC_DRAW);
//     glEnableVertexAttribArray(0);
//     glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float),
//                           (void *)0);
//     glEnableVertexAttribArray(1);
//     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float),
//                           (void *)(3 * sizeof(float)));
//   }
//   state->bindVertexArray(_vao);
//   glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// }